<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<style>
  p {
    font-size: 12px;
  }
</style>
<body>
  <h4>1. call 传参逗号分隔</h4>
  <p>测试p在浏览器最小的字体</p>

</body>
<script>

  // https://github.com/mqyqingfeng/Blog/issues/11
  
  // 首先apply是Function.prototype上的一个方法
  Function.prototype.myCall = function() {
    // 由于目标函数的实参数量是不定的，这里就不写形参了
    // 实际上通过arugments对象，我们能拿到所有实参
    // 第一个参数是绑定的this

    var thisArg = arguments[0];  //取出第一个参数， 就是call 函数指向的对象
    // 接着要判断是不是严格模式
    var isStrict = (function(){ return this === undefined })
    if(!isStrict) {
      //如果是其他原始值，需要通过构造函数包装成对象
      var thisArgType = typeof thisArg
      if(thisArgType === "number") {
        thisArg = new Number(thisArg)
      } else if(thisArgType === "string") {
        thisArg = new String(thisArg)
      } else if(thisArgType === "boolean") {
        thisArg = new Boolean(thisArg)
      }
    }

    // 截取从索引1， 因为第一个参数是call 函数指向的对象   slice() 截取数组， 返回截取的数组
    var invokeParams = [...arguments].slice(1);  //调用函数传的参数
    // 接下来要调用目标函数，那么如何获取到目标函数呢？
    // 实际上this就是目标函数，因为myCall是作为一个方法被调用的，this当然指向调用对象，而这个对象就是目标函数
    // 这里做这么一个赋值过程，是为了让语义更清晰一点

    var invokeFunc = this;
    if(thisArg === null || thisArg === undefined) {
      return invokeFunc(...invokeFunc)
    }

    console.log(invokeFunc, '目标值调用传的实参')

    // 否则，让目标函数成为thisArg对象的成员方法，然后调用它
    // 直观上来看，可以直接把目标函数赋值给对象属性，比如func属性，但是可能func属性本身就存在于thisArg对象上
    // 所以，为了防止覆盖掉thisArg对象的原有属性，必须创建一个唯一的属性名，可以用Symbol实现，如果环境不支持Symbol，可以通过uuid算法来构造一个唯一值。
    var uniquePropName = Symbol(thisArg)
    thisArg[uniquePropName] = invokeFunc

    // 返回目标函数执行的结果
    return thisArg[uniquePropName](...invokeParams)
  }

  function test(a, b) {
    var args = [].slice.myCall(arguments)
    // console.log(arguments, args)
  }
  test(1, 2)

  var obj = {
    name: 'jack'
  };
  var name = 'global';
  function getName() {
    return this.name;
  }
  getName(); // global
  getName.myCall(obj);  // jack

</script>


<script>
  Function.prototype.myCall = function (context, ...arr) {
    // context 是需要绑定的对象
    if (context === null || context === undefined) {
      // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
      context = window 
    } else {
      context = Object(context) // 值为原始值（数字，字符串，布尔值）的 this 会指向该原始值的实例对象
    }
    const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数  每个从Symbol()返回的symbol值都是唯一的 就等于 Symbol('特殊属性Symbol')

    context[specialPrototype] = this; // 函数的this指向隐式绑定到context上
    let result = context[specialPrototype](...arr); // 通过隐式绑定执行函数并传递参数
    delete context[specialPrototype]; // 删除上下文对象的属性
    return result; // 返回函数执行结果
  };


  const symbol1 = Symbol();
  const symbol2 = Symbol(42);
  const symbol3 = Symbol('foo'); 
  console.log(symbol1);  // Symbol()
  console.log(symbol2);  // Symbol(42)
  console.log(symbol3);  // Symbol(foo)
</script>
</html>